package common;

import java.util.List;
import java.util.Vector;
import java.util.ArrayList;
import java.io.File;
import java.io.FileReader;
import java.io.BufferedReader;
//import java.io.FileWriter;
//import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Queue;
import java.util.PriorityQueue;
import utils.Hashes;
import java.util.Iterator;
import quorum.QuorumTask;
import quorum.Quorum;

public class Lists {
	public static final int GROUP_SIZE = 5;
	
	private List<Entity> static_servers;

	private Vector<Queue<Entity>> live_servers;

	private List<ClientEntity> static_clients;

	private List<ClientEntity> live_clients;
	
	/**
	 * I am making it singleton: I think we only need one instance of these 4 lists
	 * */
	private static Lists instance;

	private Lists(String static_server_filename, String static_client_filename)
			throws FileNotFoundException {
		/*this is the constructor
		 * creates empty live server list and empty live client list
		 * creates static server list and static client list from local files*/
		
		live_servers = new Vector<Queue<Entity>>();
		live_clients = new ArrayList<ClientEntity>();
		static_servers = new ArrayList<Entity>();
		static_clients = new ArrayList<ClientEntity>();
		for (int i = 0; i < GROUP_SIZE; i++) {
			live_servers.add(new PriorityQueue<Entity>());
		}
		/* get static server list from local disk */
		File file = new File(static_server_filename);
		if (!file.exists()) {
			throw (new FileNotFoundException(
					"static server list file not found"));
		}
		String s;
		try {
			BufferedReader br = new BufferedReader(new FileReader(file));
			while ((s = br.readLine()) != null) {
				String tmp[] = s.split(" ");
				if (tmp.length != 2) {
					continue;
				}
				int id = (new Integer(tmp[0])).intValue();
				Entity sn = new Entity(id, tmp[1]);
				static_servers.add(sn);
			}
			br.close();
		} catch (IOException e) {
		}
		/*get static client list from local disk*/
		File file1=new File(static_client_filename);
		if(!file1.exists()){
			throw (new FileNotFoundException(
			"static client list file not found"));
		}
		try {
			BufferedReader br1 = new BufferedReader(new FileReader(file1));
			while ((s = br1.readLine()) != null) {
				String tmp[] = s.split(" ");
				if (tmp.length != 2) {
					continue;
				}
			
				int id = (new Integer(tmp[0])).intValue();
				ClientEntity cn = new ClientEntity(id, tmp[1]);
				static_clients.add(cn);
			}
			br1.close();
		} catch (IOException e) {
		}
	}
	
	public static Lists getInstance(){
		if(instance ==null)
			try{
				/**
				 * REMEMBER to change filenames
				 * */
				instance=new Lists("StaticServerList","StaticClientList");
				}catch(FileNotFoundException e){
					System.out.println("Lists cannot be loaded from disk"+e);
				}
		return instance;
		
	}
	public  synchronized Vector<Queue<Entity>> getLiveServerList(){
		return live_servers;
	}
	
	public  synchronized Queue<Entity> getLiveServerGroup(int i){
		return live_servers.get(i);
	}
	
	public synchronized  List<Entity> getLiveServerGroupinlist(int i){
		if(i>=GROUP_SIZE){return null;}
		return new ArrayList<Entity>(live_servers.get(i));
	}
	
	public synchronized  List<Entity> getStaticServerList(){
		return static_servers;
	}
	
	public  synchronized List<ClientEntity> getLiveClientList(){
		return live_clients;
	}
	
	public  synchronized List<ClientEntity> getStaticClientList(){
		return static_clients;
	}
	
	/*!! add & remove methods only deal with the lists in memory.static ones never change*/
//	public boolean addStaticServer(Entity sn){
//		return static_servers.add(sn);
//	}
//	public boolean deleteStaticServer(Entity sn){
//		return static_servers.remove(sn);
//	}
	public synchronized  boolean addLiveServer(Entity sn){
		int group=Hashes.getGroupIdFromServerId(sn.getId());
		if((live_servers.get(group)).contains(sn))
			return true;
		return (live_servers.get(group)).add(sn);
	}
	public synchronized  boolean deleteLiveServer(Entity sn){
		int group=Hashes.getGroupIdFromServerId(sn.getId());
		if(!(live_servers.get(group)).contains(sn))
			return true;
		Quorum.getInstance().getGlobalBroadcast().resetEntity(sn);
		Quorum.getInstance().getGroupBroadcast().resetEntity(sn);
		Quorum.getInstance().getSiblingBroadcast().resetEntity(sn);
		return (live_servers.get(group)).remove(sn);
	}
//	public boolean addStaticClient(ClientEntity sn){
//		return static_clients.add(sn);
//	}
//	public boolean deleteStaticClient(ClientEntity sn){
//		return static_clients.remove(sn);
//	}
	public synchronized  boolean addLiveClient(ClientEntity sn){
		System.out.println("####ADD CLIENT###"+sn);
		if(live_clients.contains(sn))
		{return true;}else{
			/*tell other servers*/
			QuorumTask task=new QuorumTask(QuorumTask.ACTION_INSERT,QuorumTask.TARGET_LIVE_CLIENTS,sn.toString());
			Quorum.getInstance().writeChildren(task);
			if(Quorum.getInstance().isLeader()){
				Quorum.getInstance().writeSibling(task);
			}
			return live_clients.add(sn);
		}
	}
	public synchronized  boolean deleteLiveClient(ClientEntity sn){
		System.out.println("####DELETE CLIENT###"+sn);
		if(!live_clients.contains(sn))
		{return true;}else{
			/*tell other servers*/
			QuorumTask task=new QuorumTask(QuorumTask.ACTION_DELETE,QuorumTask.TARGET_LIVE_CLIENTS,sn.toString());
			Quorum.getInstance().writeChildren(task);
			if(Quorum.getInstance().isLeader()){
				Quorum.getInstance().writeSibling(task);
			}
			return live_clients.remove(sn);
		}
	}
	
	public  synchronized List<Entity> responsibleServers(int clientId){
		List<Integer> groups = Hashes.getGroupIdListFromClientId(clientId);
		Lists instance=Lists.getInstance();
		for(int i=0;i<groups.size();i++){
			int ii=groups.get(i).intValue();
			if((instance.getLiveServerGroup(ii)).size()>0){
				return instance.getLiveServerGroupinlist(ii);
			}
		}
		return null;
	}
	
//	public boolean writeStaticServers(String static_server_filename)throws IOException{
//		/*write static servers to local disk*/
//		File file=new File(static_server_filename);
//		if(!file.exists()){
//			file.createNewFile();
//		}
//		BufferedWriter br=new BufferedWriter(new FileWriter(file));
//			for(int j=0;j<static_servers.size();j++){
//				br.write((static_servers.get(j)).toString());
//			}
//		br.flush();
//		br.close();
//		return true;
//	}
//	
//	public boolean writeStaticClients(String static_client_filename)throws IOException{
//		/*write static clients to local disk*/
//		File file=new File(static_client_filename);
//		if(!file.exists()){
//			file.createNewFile();
//		}
//		BufferedWriter br=new BufferedWriter(new FileWriter(file));
//
//			for(int j=0;j<static_clients.size();j++){
//				br.write((static_clients.get(j)).toString());
//			}
//		
//		br.flush();
//		br.close();
//		return true;
//	}
	public synchronized  String liveString(){
		String res="server";
		//server@id ip@id ip@..@client@id pw@id pw
		for(int i=0;i<GROUP_SIZE;i++){
			for(Iterator<Entity> it=getLiveServerGroup(i).iterator();it.hasNext();){
				Entity e=it.next();
				res=res+"@"+e.getId()+" "+e.getIp();
			}
		}
		res=res+"@client";
		for(Iterator<ClientEntity> it=getLiveClientList().iterator();it.hasNext();){
			ClientEntity e=it.next();
			res=res+"@"+e.getId()+" "+e.getPassword();
		}
		System.out.println("==lives==\n"+res);
		return res;
	}
	
	public synchronized void constructLive(String list){
		live_clients.clear();
		for(int y=0;y<GROUP_SIZE;y++){
		live_servers.get(y).clear();
		}
		String[] contents=list.split("@");
		boolean isserver=true;
		for(int i=0;i<contents.length;i++){
			if(contents[i].equals("server")){
				isserver=true;
				continue;
			}
			if(contents[i].equals("client")){
				isserver=false;
				continue;
			}
			String[] a=contents[i].split(" ");
			if(isserver){
				addLiveServer(new Entity(Integer.valueOf(a[0]).intValue(),a[1]));
			}else{
				addLiveClient(new ClientEntity(Integer.valueOf(a[0]).intValue(),a[1]));
			}
		}
	}
}